package com.gitee.wsl.data.fetcher

import com.gitee.wsl.data.api.RefreshAble
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlin.time.Duration
import kotlin.time.ExperimentalTime

/**
 * Represents a fetching logic encapsulating a suspend function to fetch data.
 *
 * @param T The type of data to be fetched.
 * @property fetcher A suspend function that returns a [FetcherResult] representing the fetched data.
 */
data class Fetcher<T: Any>(
    val fetcher: suspend () -> FetcherResult<T>,
) {
    companion object {
        fun <T: Any> ofResult(fetcher: suspend () -> FetcherResult<T>) = Fetcher(fetcher)

        fun <T: Any> of(fetcher: suspend () -> T) = Fetcher {
            try {
                FetcherResult.Data(fetcher())
            } catch (e: Exception) {
                FetcherResult.Error.Exception(e)
            }
        }
    }
}

@OptIn(ExperimentalTime::class)
fun<T: Any> Fetcher<T>.stream(
    cache: BlockedCache<T>,
    condition: Flow<Boolean> = flowOf(true),
    forceRefresh: Boolean = false,
    updateData:((BlockedCacheData<T>) -> Unit) = { }
) = cache.getData(
        forceRefresh = forceRefresh,
        fetcher = fetcher,
        updateData = updateData,
        condition = condition,
    )

@OptIn(ExperimentalTime::class)
fun<T: Any> Fetcher<T>.stream(
    cache: BlockedCache<T>,
    condition: Flow<Boolean> = flowOf(true),
    fetch: Fetch,
    updateData:((BlockedCacheData<T>) -> Unit) = { }
) = cache.getData(
    forceRefresh = fetch is Fetch.Force,
    forceRefreshDelay = (fetch as? Fetch.Force)?.minimumDelay,
    fetcher = fetcher,
    updateData = updateData,
    predicate = { _, _ ->
        if (fetch is Fetch.Cached) {
            !fetch.ignoreExpiration // never fetch from network if ignoreExpiration is true
        } else true
    },
    condition = condition.map { it && fetch != Fetch.NoFetch },
)

/**
 * interface CoffeeApi {
 *     suspend fun hotCoffee(): List<Coffee>
 * }
 *
 * val fetcher = Fetcher.of { api.hotCoffee() }
 *
 * val (dataFlow,refreshFun) = fetcher.outputFlow(
 *             refreshTime = 5.minutes,
 *             name = "hotCoffee",
 *             isDebugEnabled = true
 *             )
 *
 * val coffee = dataFlow.mapNotNull { it.actualOrStaleData?.firstOrNull { it.id == id }}
 *
 * val output = coffee.map { coffee ->
 *         Output(
 *             title = coffee.title,
 *             description = coffee.description,
 *             ingredients = coffee.ingredients.takeIf { it.isNotEmpty() }?.let {
 *                  "Ingredients: ${it.joinToString(", ")}"
 *             } ?: "No ingredients",
 *         )
 *     }.distinctUntilChanged()
 */

fun<T: Any> Fetcher<T>.outputFlow(
    refreshTime: Duration,
    expireTime: Duration? = null,
    trigger: Flow<Unit> = flowOf(Unit),
    name: String = "genericBlockedCache",
    isDebugEnabled: Boolean = false,
    condition: Flow<Boolean> = flowOf(true),
    forceRefresh: Boolean = false,
    refreshTrigger: StateFlow<FetchRefreshTrigger> = FetchRefreshTrigger.refreshTriggerFlow()
): Pair<Flow<CacheResult<T>>, RefreshAble> {
    val dataFlow = MutableStateFlow(BlockedCacheData<T>())
    val dataCache = BlockedCache(refreshTime,expireTime, trigger, dataFlow, name, isDebugEnabled)
    val stream = stream(dataCache, condition, forceRefresh){
        dataFlow.value = it
    }

    return refreshTrigger.createRefreshableFetchFlow{ stream } to dataCache
}

